В задании предлагается с помощью визуального анализа ответить на несколько вопросов по данным о сердечно-сосудистых заболеваниях. Данные использовались в соревновании ML Boot Camp 5 (качать их не надо, они уже есть в репозитории).
Заполните код в клетках (где написано "Ваш код здесь") и ответьте на вопросы в веб-форме. Код отправлять никуда не нужно.
В соревновании предлагалось определить наличие/отсутствие сердечно-сосудистых заболеваний (ССЗ) по результатам осмотра пациента.
Описание данных.
Датасет сформирован из реальных клинических анализов, и в нём используются признаки, которые можно разбить на 3 группы:
Объективные признаки:
Результаты измерения:
Субъективные признаки (со слов пациентов):
Целевой признак (который интересно будет прогнозировать):
Возраст дан в днях. Значения показателей холестерина и глюкозы представлены одним из трех классов: норма, выше нормы, значительно выше нормы. Значения субъективных признаков — бинарны.
Все показатели даны на момент осмотра.
In [6]:
# подгружаем все нужные пакеты
import pandas as pd
import numpy as np
# игнорируем warnings
import warnings
warnings.filterwarnings("ignore")
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker
%matplotlib inline
# настройка внешнего вида графиков в seaborn
sns.set_context(
"notebook",
font_scale = 1.5,
rc = {
"figure.figsize" : (12, 9),
"axes.titlesize" : 18
}
)
В рамках задания для простоты будем работать только с обучающей выборкой. Чистить данные от выбросов и ошибок в данных НЕ нужно, кроме тех случаев, где об этом явно указано.
Все визуализации рекомендуем производить с помощью библиотеки Seaborn
.
In [3]:
train = pd.read_csv('mlbootcamp5_train.csv', sep=';',
index_col='id')
In [4]:
print('Размер датасета: ', train.shape)
train.head()
Out[4]:
Для начала всегда неплохо бы посмотреть на значения, которые принимают переменные.
Переведем данные в "Long Format"-представление и отрисуем с помощью factorplot количество значений, которые принимают категориальные переменные.
In [4]:
train_uniques = pd.melt(frame=train, value_vars=['gender','cholesterol',
'gluc', 'smoke', 'alco',
'active', 'cardio'])
train_uniques = pd.DataFrame(train_uniques.groupby(['variable',
'value'])['value'].count()) \
.sort_index(level=[0, 1]) \
.rename(columns={'value': 'count'}) \
.reset_index()
sns.factorplot(x='variable', y='count', hue='value',
data=train_uniques, kind='bar', size=12);
Видим, что классы целевой переменной cardio
сбалансированы, отлично!
Можно также разбить элементы обучающей выборки по значениям целевой переменной: иногда на таких графиках можно сразу увидеть самый значимый признак.
In [5]:
train_uniques = pd.melt(frame=train, value_vars=['gender','cholesterol',
'gluc', 'smoke', 'alco',
'active'],
id_vars=['cardio'])
train_uniques = pd.DataFrame(train_uniques.groupby(['variable', 'value',
'cardio'])['value'].count()) \
.sort_index(level=[0, 1]) \
.rename(columns={'value': 'count'}) \
.reset_index()
sns.factorplot(x='variable', y='count', hue='value',
col='cardio', data=train_uniques, kind='bar', size=9);
Видим, что в зависимости от целевой переменной сильно меняется распределение холестерина и глюкозы. Совпадение?
Немного статистики по уникальным значениям признаков.
In [6]:
for c in train.columns:
n = train[c].nunique()
print(c)
if n <= 3:
print(n, sorted(train[c].value_counts().to_dict().items()))
else:
print(n)
print(10 * '-')
Итого:
Для того чтобы лучше понять признаки в датасете, можно посчитать матрицу коэффициентов корреляции между признаками.
Постройте heatmap корреляционной матрицы. Матрица формируется средствами Pandas
, со стандартным значением параметров.
In [11]:
import seaborn
In [14]:
# Правильный ответ, больше всего коррелируют признаки Weight и Gender
df = pd.read_csv('mlbootcamp5_train.csv', sep=';')
corr = train.corr()
sns.heatmap(corr)
corr
Out[14]:
Как мы увидели, в процессе исследования уникальных значений пол кодируется значениями 1 и 2, расшифровка изначально не была нам дана в описании данных, но мы догадались, кто есть кто, посчитав средние значения роста (или веса) при разных значениях признака gender
. Теперь сделаем то же самое, но графически.
Постройте violinplot для роста и пола. Используйте:
Для корректной отрисовки, преобразуйте DataFrame в "Long Format"-представление с помощью функции melt в pandas.
еще один пример
In [17]:
longformat = pd.melt(frame=df, value_vars='height', id_vars='gender')
longformat.head()
Out[17]:
Постройте на одном графике два отдельных kdeplot роста, отдельно для мужчин и женщин. На нем разница будет более наглядной, но нельзя будет оценить количество мужчин/женщин.
In [19]:
sns.violinplot(data=longformat, x='variable', y='value', hue='gender', scale='count');
plt.show()
In [20]:
sns.kdeplot(df[df['gender'] == 1]['height'])
sns.kdeplot(df[df['gender'] == 2]['height'])
plt.show()
В большинстве случаев достаточно воспользоваться линейным коэффициентом корреляции Пирсона для выявления закономерностей в данных, но мы пойдём чуть дальше и используем ранговую корреляцию, которая поможет нам выявить пары, в которых меньший ранг из вариационного ряда одного признака всегда предшествует большему другого (или наоборот, в случае отрицательной корреляции).
In [21]:
sns.heatmap(df.corr(method='spearman'));
plt.show()
In [26]:
df.corr(method='spearman')
Out[26]:
Постройте совместный график распределения jointplot двух наиболее коррелирующих между собой признаков (по Спирмену).
Кажется, наш график получился неинформативным из-за выбросов в значениях. Постройте тот же график, но с логарифмической шкалой (чтобы не получать OverflowError необходимо отфильтровать значения меньше либо равные нулю).
In [27]:
logged = train.loc[ (train['ap_hi'] > 0)&(train['ap_lo']>0), ['ap_hi', 'ap_lo'] ]
logged['ap_hi'] = np.log(logged['ap_hi'])
logged['ap_lo'] = np.log(logged['ap_lo'])
g = sns.jointplot(x = 'ap_hi', y = 'ap_lo', data = logged)
# -------------------- #
"""Сетка"""
g.ax_joint.grid(True)
"""Преобразуем логарифмические значения на шкалах в реальные"""
g.ax_joint.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, pos: str(round(int(np.exp(x))))))
g.ax_joint.xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, pos: str(round(int(np.exp(x))))))
Посчитаем, сколько полных лет было респондентам на момент их занесения в базу.
In [28]:
train['age_years'] = (train['age'] // 365.25).astype(int)
Постройте Countplot, где на оси абсцисс будет отмечен возраст, на оси ординат – количество. Каждое значение возраста должно иметь два столбца, соответствующих количеству человек каждого класса cardio (здоров/болен) данного возраста.
In [39]:
sns.set_context(
"notebook",
font_scale = 1,
rc = {
"figure.figsize" : (12, 9),
"axes.titlesize" : 18
}
)
In [40]:
train['age_years'] = (train['age'] // 365.25).astype(int)
sns.countplot(x='age_years', hue = 'cardio', data = train)
Out[40]:
In [41]:
a = pd.crosstab(train.age_years, train.cardio).reset_index()
a[a[0] < a[1]]
Out[41]:
In [ ]: